Errors should never pass silently. Unless explicitly silenced. ~ Zen of Python
Hi guys, last lecture we looked at common error messages, in this lecture we shall look at a neat way to handle those errors. The Syntax:
try:
{code block}
except {Error}:
{code block}
Okay, so what does try & except actually do? Well basically, Python tries to execute a statement, but if in the process of executing that statement an error (of type Error) occurs then we to something else instead. In terms of logic, try/except works a bit like if/elif work. Here is a simple example:
In [1]:
a_list = [10, 32.4, -14.2, "a", "b", [], [1,2]]
for item in a_list:
try:
print(item * item)
except TypeError:
print(item + item)
So what’s going on here? Well, we have list which contains several different data-types. For every 'item' we try to multiply item by itself. If 'item' is a number this makes sense and so we print item * item. However, if we try to multiply a string by a string we get a TypeError, which the except statement catches. So if we receive a TypeError we try something else, in this particular case we add item to item, and thus "aa", "bb", etc get printed.
Now it is important to note this current code is only set up to handle TypeErrors. What happens if we change the above bit of code to divide rather than multiply and feed it 0?
In [4]:
item = 0
try:
item / item
except TypeError:
print(item + item)
In this case Python didn't receive a TypeError and thus the except block of code failed to execute. Now, we can fix this code in one of two ways:
In [18]:
x = 0
# The bad fix first...
try:
x / x
except:
print("Bad ", x + x)
# The Good fix...
try:
item / item
except (TypeError, ZeroDivisionError): # please note the "SnakeCase".
print("Good", x + x)
The bad fix just leaves a blank except statement, this catches ALL errors. The good fix meanwhile specifically states what errors it should catch, but the code will still fail if the error is something other than Type or dividing by zero.
So why is it bad to leave a bare except statement? Well, as I've stated elsewhere in these lecture series it is often better to crash than it is to output junk. And please trust me when I say bare except clauses are a great way to output junk.
In the second case we specifically state the errors we expect to sometimes receive. And this is nice for a few reasons, first, by naming the exceptions the code is a bit more readable. Secondly, expressly stating the errors forces you to be much more mindful when writing the code in the first place. And thirdly, if something unexpected does happen then, unless you have bare except statements throughout all of your code eventually you'll pass junk data to some function and get an error there. So basically in many cases you are not really solving the problem, you end up delaying the problem.
In short, for most applications you should be looking to handle the minimum number of cases you need for the code to function and in all other cases just let it crash.
Okay, how about one more example?
In [36]:
def character_movement(x, y):
"""where (x,y) is the position on a 2-d plane"""
return [("start", (x, y)),
("left", (x -1, y)),("right", (x + 1, y)),
("up", (x, y - 1)), ("down", (x, y + 1))]
the_map = [ [0, 0, 0],
[0, 0, 0],
[0, 0, 1]] # 1 denotes our character
moves = character_movement(2, 2)
print("Starting square = (2,2)")
for (direction, position) in moves[1:]:
print("Trying to move '{}' to square {}:".format(direction, position))
try:
the_map[position[1]][position[0]] = 2
print(*the_map, sep="\n")
print("\n")
except IndexError:
print("Square {}, is out of bounds. IndexError sucessfully caught.\n".format(position))